// Copyright 2010, Vanya Davidenko.
// Используемая кодировка: UTF-8.

#ifndef LIBV_CHECK_H_
#define LIBV_CHECK_H_

#include <cstdio>
#include <cstdlib>  // exit
#include <stdexcept>
#include <sstream>
#include <libv/stdint.h>

#include <assert.h>  // check.h должен предоставлять assert
#include <libv/static_assert.h>


#ifndef DISABLE_PRECOND_CHECK
# define PRECOND(expr)         libv::Condition<libv::PrecondException>(expr, #expr, "", __FILE__, __LINE__)
# define PRECOND_MSG(expr,msg) libv::Condition<libv::PrecondException>(expr, #expr, msg, __FILE__, __LINE__)
# define PRECOND_EQ(v,w) PRECOND(v==w)
# define PRECOND_NE(v,w) PRECOND(v!=w)
# define PRECOND_LE(v,w) PRECOND(v<=w)
# define PRECOND_GE(v,w) PRECOND(v>=w)
# define PRECOND_LT(v,w) PRECOND(v<w)
# define PRECOND_GT(v,w) PRECOND(v>w)
# define PRECOND_NZ(v)   PRECOND(v!=0)
#else
# define PRECOND(expr)
# define PRECOND_MSG(expr,msg)
# define PRECOND_EQ(v,w)
# define PRECOND_NEQ(v,w)
# define PRECOND_LEQ(v,w)
# define PRECOND_GEQ(v,w)
# define PRECOND_LT(v,w)
# define PRECOND_GT(v,w)
# define PRECOND_NZ(v)
#endif


// VERIFY отключать нельзя, так как вызовы могут производиться внутри него.
#define VERIFY(expr)         libv::Condition<libv::VerifyException>(expr, #expr, "", __FILE__, __LINE__)
#define VERIFY_MSG(expr,msg) libv::Condition<libv::VerifyException>(expr, #expr, msg, __FILE__, __LINE__)


/*
// VERIFY отключать нельзя, так как вызовы могут производиться внутри него.
#ifndef DISABLE_LOG_ERROR_CHECK
# define LOG_ERROR(expr)         libv::Condition<libv::VerifyException>(expr, #expr, "", __FILE__, __LINE__)
# define LOG_ERROR(expr,msg) libv::Condition<libv::VerifyException>(expr, #expr, msg, __FILE__, __LINE__)
#else
# define LOG_ERROR(expr)
# define LOG_ERROR(expr,msg)
#endif*/



namespace libv
{

/** Базовое исключение, генерируемое при обнаружении ошибок в проверках. */
class CheckException : public ::std::exception
{
  public:
    explicit CheckException(const ::std::string& msg)
        : msg_(::std::string("Check : ") + msg) {}

    /** Генерация строки с ошибок вида: Check : X : ... : Y : MESSAGE,
      * где X, Y  -- иерархия классов исключений,
      * а MESSAGE передаваемое сообщение. */
    virtual const char* what() const throw() { return msg_.c_str(); }

    virtual ~CheckException() throw() {}

  private:
    ::std::string msg_;
};



/** Исключение, генерируемое при ложном условии в макросе VERIFY. */
class VerifyException : public CheckException
{
  public:
    explicit VerifyException(const ::std::string& msg) throw()
        : CheckException(::std::string("Verify : ") + msg) {}

    virtual ~VerifyException() throw() {}
};


/** Исключение, генерируемое при ложном условии в макросе PRECOND. */
class PrecondException : public CheckException
{
  public:
    explicit PrecondException(const ::std::string& msg) throw()
        : CheckException(::std::string("Precond : ") + msg) {}

    virtual ~PrecondException() throw() {}
};




/** Проверка условия и
  *   - вывод сообщения об ошибке и завершение (exit) программы (в дебаге)
  *   - генерация исключения TCheckException с методом what() (в релизе)
  * в случае его ложности.
  * @param[in] cond проверяемое условие
  * @param[in] expr строка с условие (полученная препроцессором)
  * @param[in] msg опциональное пояснительное сообщение
  * @param[in] module_name имя модуля, в котором осуществляется проверка
  * @param[in] module_line соответствующий номер строки в модуле.
  * @exception не в отладочном режиме (релиз): TCheckException
  *            (шаблон класса, унаследованного от libv::CheckException). */
template<class TCheckException>
inline void Condition(const bool cond,
                      const char* const expr,
                      const char* const msg,
                      const char* const module_name,
                      const uint32_t module_line)
{
    if ( cond ) return;

    ::std::stringstream exception_msg;
    exception_msg << expr << ", "
                  << msg << " @ "
                  << module_name << ":" << module_line;
    const ::std::string ex_msg = exception_msg.str();
    Log(ex_msg, TCheckException(ex_msg));
    throw TCheckException(ex_msg);
}


inline void Log(const ::std::string& msg, VerifyException)
{
    // VERIFY используется как неотключаемый в релизе assert.
    // Он чаще всего не обрабатывается и его генерация приводит к завершению
    // приложения. На всякий случай запишем причину в лог.
    ::std::fprintf(stderr, "VERIFY failure: %s", msg.c_str());
}


inline void Log(const ::std::string&, PrecondException)
{
    // Предусловия с большей вероятностью будут обрабатываться и выводить в лог
    // ошибку незачем.
}






/** Завершение программы из-за достижения некорректной точки выполнения. */
inline void Die(const char* msg = "")
{
    ::std::fprintf(stderr, "libv::Die called: %s\n", msg);
    ::std::exit(-666);
}

}  // libv

#endif  // LIBV_CHECK_H_
